package com.wcs.exam.common.server.upload.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.wcs.exam.common.server.upload.Upload;
import com.wcs.exam.common.server.upload.UploadFace;
import com.wcs.exam.common.util.FileUtils;
import com.wcs.exam.common.util.ImgUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author wcs
 */
@Slf4j
@Component(value = "aliyun")
public class AliyunUploadImpl implements UploadFace {

    @Override
    public String uploadPic(MultipartFile file, Upload upload) {
        return putObject(file, upload, CannedAccessControlList.PublicRead);
    }

    @Override
    public String uploadPicBase64(String imgBase64, Upload upload) {
        // 获取图片类型
        String type = ImgUtil.getBase64Type(imgBase64);
        if (StrUtil.isBlank(type)) {
            type = "jpg";
        }
        return uploadPicBase64(imgBase64, FileUtils.getFileName(type), upload);
    }

    @Override
    public String uploadPicBase64(String imgBase64, String fileName, Upload upload) {
        // 解码Base64字符串
        byte[] data = Base64.getDecoder().decode(imgBase64);
        //  创建字节数组输入流
        InputStream inputStream = new ByteArrayInputStream(data);
        // 上传文件
        return putObject(inputStream, fileName, upload, CannedAccessControlList.PublicRead);
    }

    @Override
    public String uploadDoc(MultipartFile file, Upload upload, Boolean isPublicRead) {
        return putObject(file, upload, isPublicRead ? CannedAccessControlList.PublicRead : CannedAccessControlList.Private);
    }

    @Override
    public String uploadDoc(File file, String fileName, Upload upload, Boolean isPublicRead) {
        return putObject(file, fileName, upload, isPublicRead ? CannedAccessControlList.PublicRead : CannedAccessControlList.Private);
    }

    @Override
    public void delete(String fileUrl, Upload upload) {
        try {
            String filePath = fileUrl.replace(upload.getAliyunOssUrl(), "");
            OSS ossClient = getOssClient(upload.getMinioEndpoint(), upload.getAliyunAccessKeyId(), upload.getAliyunAccessKeySecret(), upload.getAliyunOssBucket());
            ossClient.deleteObject(upload.getAliyunOssBucket(), filePath);
        } catch (Exception e) {
            log.error("删除失败", e);
        }
    }

    @Override
    public void delete(Collection<String> fileUrls, Upload upload) {
        List<String> keyList = new ArrayList<>();
        for (String url : fileUrls) {
            keyList.add(url.replace(upload.getAliyunOssUrl(), ""));
            if (keyList.size() >= 1000) {
                deleteBatch(keyList, upload);
                keyList.clear();
            }
        }
        if (CollectionUtil.isNotEmpty(keyList)) {
            deleteBatch(keyList, upload);
        }
    }

    @Override
    public String getDownloadUrl(String docUrl, int expireSeconds, Upload upload) {
        return "";
    }

    @Override
    public String getPreviewConfig(String docUrl, int expireSeconds, Upload upload) {
        return "";
    }


    /**
     * 存入OSS
     */
    private static String putObject(File file, String fileName, Upload upload, CannedAccessControlList acl) {
        try (InputStream inputStream = new FileInputStream(file)) {
            return putObject(inputStream, fileName, upload, acl);
        } catch (Exception e) {
            log.error("上传失败", e);
            return "";
        }
    }

    /**
     * 存入OSS
     */
    private static String putObject(MultipartFile file, Upload upload, CannedAccessControlList acl) {
        try (InputStream inputStream = file.getInputStream()) {
            String name = file.getOriginalFilename();
            return putObject(inputStream, name, upload, acl);
        } catch (Exception e) {
            log.error("上传失败", e);
            return "";
        }
    }


    private static String putObject(InputStream inputStream, String fileName, Upload upload, CannedAccessControlList cannedAcl) {
        try {
            String filePath = FileUtils.getFilePath(FileUtils.PREFIX, FileUtils.getFileName(fileName.substring(fileName.lastIndexOf(".") + 1)));
            OSS ossClient = new OSSClientBuilder().build(upload.getAliyunOssEndpoint(), upload.getAliyunAccessKeyId(), upload.getAliyunAccessKeySecret());
            ObjectMetadata meta = new ObjectMetadata();
            meta.setContentEncoding("UTF-8");
            meta.setContentDisposition("attachment;filename={}".replace("{}", URLEncoder.encode(fileName, StandardCharsets.UTF_8)));
            meta.setObjectAcl(cannedAcl);
            ossClient.putObject(upload.getAliyunOssBucket(), filePath, inputStream, meta);
            return FileUtils.getFilePath(upload.getAliyunOssUrl(), filePath);
        } catch (Exception e) {
            log.error("上传失败", e);
            return "";
        }
    }

    /**
     * 批量删除文件
     *
     * @param keyList 文件列表
     * @param upload  存储配置
     */
    private static void deleteBatch(List<String> keyList, Upload upload) {
        OSS ossClient = getOssClient(upload.getMinioEndpoint(), upload.getAliyunAccessKeyId(), upload.getAliyunAccessKeySecret(), upload.getAliyunOssBucket());
        ossClient.deleteObjects(new DeleteObjectsRequest(upload.getAliyunOssBucket()).withKeys(keyList));
    }

    /**
     * 删除Bucket中的Object
     */
    private static OSS getOssClient(String endpoint, String keyId, String keySecret, String bucketName) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, keyId, keySecret);
        if (!ossClient.doesBucketExist(bucketName)) {
            // 创建CreateBucketRequest对象。
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            // 如果创建存储空间的同时需要指定存储类型和数据容灾类型, 请参考如下代码。
            // 此处以设置存储空间的存储类型(标准存储：Standard、低频访问：Infrequent Access、归档存储：Archive、冷归档存储：Cold Archive)。
            createBucketRequest.setStorageClass(StorageClass.Standard);
            // 数据容灾类型默认为本地冗余存储，即DataRedundancyType.LRS。如果需要设置数据容灾类型为同城冗余存储，请设置为DataRedundancyType.ZRS。
            createBucketRequest.setDataRedundancyType(DataRedundancyType.LRS);
            // 设置存储空间的权限为公共读，默认为私有(公共读写：public-read-write、公共读：public-read、私有读写：private)。
            createBucketRequest.setCannedACL(CannedAccessControlList.Private);
            // 创建存储空间。
            ossClient.createBucket(createBucketRequest);
            // 跨域处理
            SetBucketCORSRequest request = new SetBucketCORSRequest(bucketName);
            ArrayList<SetBucketCORSRequest.CORSRule> putCorsRules = new ArrayList<>();
            SetBucketCORSRequest.CORSRule corRule = new SetBucketCORSRequest.CORSRule();
            List<String> allowedOrigin = Collections.singletonList("*");
            List<String> allowedMethod = Arrays.asList("GET", "PUT", "DELETE", "POST", "HEAD");
            List<String> allowedHeader = Collections.singletonList("*");
            List<String> exposedHeader = Arrays.asList("ETag", "x-oss-request-id", "x-oss-version-id");
            Integer maxAgeSeconds = 0;
            // AllowedOrigins和AllowedMethods最多支持一个星号（*）通配符。星号（*）表示允许所有的域来源或者操作。
            corRule.setAllowedMethods(allowedMethod);
            corRule.setAllowedOrigins(allowedOrigin);
            // AllowedHeaders和ExposeHeaders不支持通配符。
            corRule.setAllowedHeaders(allowedHeader);
            corRule.setExposeHeaders(exposedHeader);
            // 指定浏览器对特定资源的预取（OPTIONS）请求返回结果的缓存时间，单位为秒。
            corRule.setMaxAgeSeconds(maxAgeSeconds);
            // 最多允许10条规则。
            putCorsRules.add(corRule);
            // 已存在的规则将被覆盖。
            request.setCorsRules(putCorsRules);
            ossClient.setBucketCORS(request);
        }
        return ossClient;
    }
}
